package com.zt.questionnaire.service;

import cn.hutool.core.util.ReflectUtil;
import com.zt.questionnaire.common.bean.question.AnswerBean;
import com.zt.questionnaire.common.config.redis.CacheOperator;
import com.zt.questionnaire.common.enums.QuestionTypeEnum;
import com.zt.questionnaire.common.utils.*;
import com.zt.questionnaire.db.entity.ReportEntity;
import com.zt.questionnaire.db.entity.ReportOnboardingEntity;
import com.zt.questionnaire.db.entity.UserEntity;
import com.zt.questionnaire.db.mapper.ReportMapper;
import com.zt.questionnaire.model.ReportReflect;
import com.zt.questionnaire.model.excel.questionnaire.QuestionnaireOptionExcel;
import com.zt.questionnaire.model.excel.questionnaire.QuestionnaireQuestionExcel;
import com.zt.questionnaire.model.excel.questionnaire.QuestionnaireTypeExcel;
import com.zt.questionnaire.service.excel.HExcelQuestionnaireService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 报告表(3套问卷基础信息表
 *
 * @author funnywus
 * @email funnywus@163.com
 * @date 2023-09-04 14:27:38
 */
@Slf4j
@Service
public class ReportService {

    @Autowired
    private ReportMapper mapper;

    @Autowired
    private CacheOperator redis;
    @Lazy
    @Autowired
    private ReportOnboardingService reportOnboardingService;
    @Autowired
    private HExcelQuestionnaireService excelQuestionnaireService;
    @Autowired
    private UserService userService;

    /**
     * 过期时间
     **/
    public long timeOut = TimeUtils.DAY;

    // userId_typeId
    private static final String KEY = "report@%d";
    private static final String USER_TYPE_KEY = "report@%d_%d";

    /**
     * 用户-报告类型key
     *
     * @return
     */
    private static String formatUserTypeKey(Long userId, Integer type) {
        return String.format(USER_TYPE_KEY, userId, type);
    }

    /**
     * 组装主键key
     *
     * @return
     */
    private static String formatKey(Long id) {
        return String.format(KEY, id);
    }

    public ReportEntity get(Long userId, Integer type) {
        ReportEntity obj = getUserTypeRedis(userId, type);
        if (obj != null) {
            return obj;
        }
        obj = getDb(userId, type);
        if (obj != null) {
            setUserTypeRedis(obj);
            setRedis(obj);
        }
        return obj;
    }

    public ReportEntity getDb(Long userId, Integer type) {
        ReportEntity obj = new ReportEntity();
        obj.setUserId(userId);
        obj.setType(type);
        return mapper.selectOne(obj);
    }

    public ReportEntity getDb(Long id) {
        return mapper.selectByPrimaryKey(id);
    }

    /**
     * 保存
     *
     * @param obj
     * @return
     */
    public boolean save(ReportEntity obj) {
        obj.setSubmitTime(TimeUtils.getCurrTime());
        boolean is = saveDb(obj);
        if (!is) {
            return false;
        }
        setUserTypeRedis(obj);
        setRedis(obj);
        return true;
    }

    public boolean saveDb(ReportEntity obj) {
        return mapper.insert(obj) > 0;
    }

    /**
     * 更新
     *
     * @param obj
     * @return
     */
    public boolean update(ReportEntity obj) {
        boolean is = updateDb(obj);
        if (!is) {
            return false;
        }
        setUserTypeRedis(obj);
        setRedis(obj);
        return true;
    }

    public boolean updateDb(ReportEntity obj) {
        return mapper.updateByPrimaryKey(obj) > 0;
    }

    private ReportEntity getUserTypeRedis(Long userId, Integer type) {
        String formatKey = formatUserTypeKey(userId, type);
        return redis.getObject(formatKey, ReportEntity.class, timeOut);
    }

    private void setUserTypeRedis(ReportEntity obj) {
        String formatKey = formatUserTypeKey(obj.getUserId(), obj.getType());
        redis.setObject(formatKey, obj, timeOut);
    }

    private ReportEntity getRedis(Long id) {
        String formatKey = formatKey(id);
        return redis.getObject(formatKey, ReportEntity.class, timeOut);
    }

    private void setRedis(ReportEntity obj) {
        String formatKey = formatKey(obj.getId());
        redis.setObject(formatKey, obj, timeOut);
    }

    /**
     * 保存报告
     *
     * @return
     */
    public boolean save(ReportEntity base, Long userId) {
        Integer type = base.getType();
        base.setUserId(userId);
        boolean isExist = isExist(type, userId);
        AssertUtils.isTrue(isExist, "该用户已提交过该问卷");

        ReportReflect reportReflect = new ReportReflect();

        // 处理问题,答案，需要被反射的字段值
        handleQuestionList(base, reportReflect);

        if (HExcelQuestionnaireService.QUESTIONNAIRE_TYPE_ONBORADING == type) {
            return reportOnboardingService.save(base, reportReflect);
        } else {
            return save(base, reportReflect);
        }
    }

    private void handleQuestionList(ReportEntity base, ReportReflect reportReflect) {
        List<List<AnswerBean>> answerLists = base.getAnswerLists();
        Integer type = base.getType();

        List<ReportEntity.Question> questionListDb = new ArrayList<>();
        List<List<ReportEntity.Answer>> answerListsDb = new ArrayList<>();
        base.setQuestionList(questionListDb);
        base.setAnswerList(answerListsDb);

        QuestionnaireTypeExcel questionnaireType = excelQuestionnaireService.getQuestionnaireType(type);
        AssertUtils.isNull(questionnaireType, "问卷不存在");

        List<String> questionIdList = questionnaireType.getQuestionIdList();

        // 校验问题，收集问题、答案数据
        String msg = handleQuestion(base, reportReflect, questionIdList, answerLists);
        AssertUtils.isNotBlank(msg, msg);
    }

    /**
     * 提交问题选项，对问题进行处理。
     *
     * @param base
     * @param reportReflect
     * @param questionIdList 问题id集合
     * @param answerLists
     * @return
     */
    private String handleQuestion(ReportEntity base, ReportReflect reportReflect, List<String> questionIdList,
                                  List<List<AnswerBean>> answerLists) {

        AtomicInteger i = new AtomicInteger(0);
        Map<String, QuestionnaireQuestionExcel> questionMap = excelQuestionnaireService.getQuestionMap();
        Map<String, QuestionnaireOptionExcel> optionMap = excelQuestionnaireService.getOptionMap();
        for (String qusId : questionIdList) {
            QuestionnaireQuestionExcel question = questionMap.get(qusId);
            // 过滤展示题
            if (question != null && Objects.equals(question.getType(), QuestionTypeEnum.SHOW.getCode())) {
                continue;
            }
            String msg = handleSingleQuestion(qusId, i, answerLists, base, reportReflect, questionMap, optionMap);
            if (msg != null) {
                return msg;
            }
        }

        return null;
    }


    /**
     * 处理单问题
     *
     * @param qusId
     * @param i
     * @param base
     * @param reportReflect
     * @param questionMap
     * @param optionMap
     * @return
     */
    private String handleSingleQuestion(String qusId, AtomicInteger i, List<List<AnswerBean>> answerLists,
                                        ReportEntity base, ReportReflect reportReflect,
                                        Map<String, QuestionnaireQuestionExcel> questionMap,
                                        Map<String, QuestionnaireOptionExcel> optionMap) {

        int index = i.get();
        if (index >= answerLists.size()) {
            AssertUtils.newThrow("问题和选项不匹配");
        }

        List<ReportEntity.Question> questionListDb = base.getQuestionList();
        List<List<ReportEntity.Answer>> answerListsDb = base.getAnswerList();

        int andIncrement = i.getAndIncrement();
        // 答案列表
        List<AnswerBean> answerList = answerLists.get(andIncrement);

        // 问题excel 对象
        QuestionnaireQuestionExcel questionnaireQuestion = questionMap.get(qusId);
        String type = questionnaireQuestion.getType();
        String quesTitle = questionnaireQuestion.getName();
        List<String> optionIdList = questionnaireQuestion.getOptionIdList();

        ReportEntity.Question ques = new ReportEntity.Question();
        ques.setType(type);
        questionListDb.add(ques);

        List<ReportEntity.Answer> ansList = new ArrayList<>();
        answerListsDb.add(ansList);

        // 设置数据库所需要的值
        String filedName = questionnaireQuestion.getFiled_name();
        setFieldValue(filedName, answerList, reportReflect, optionMap);

        // 单选题
        if (Objects.equals(type, QuestionTypeEnum.ONE.getCode())) {
            if (answerList.size() > 1) {
                log.error("问题和选项不匹配2 qusId={}, answerList={}", qusId, JsonUtils.toString(answerList));
                AssertUtils.newThrow("问题：" + questionnaireQuestion.getName() + "为单选问题，答案只能有一个");
            }
            ques.setTitle(quesTitle);
            AnswerBean answerBean = answerList.get(0);
            String ansId = answerBean.getId();
            ReportEntity.Answer ans = new ReportEntity.Answer();
            QuestionnaireOptionExcel questionnaireOption = optionMap.get(ansId);
            if (questionnaireOption == null) {
                log.error("问题和选项不匹配3 index={}, qusId={}, answerList={}", andIncrement + 1, qusId, JsonUtils.toString(answerList));
                AssertUtils.newThrow("选项不存在");
            }
            String filedValue = questionnaireOption.getFiled_value();
            if (StringUtils.isNotBlank(filedValue)) {
                ans.setContent(filedValue);
            } else {
                ans.setContent(questionnaireOption.getContent());
            }
            ansList.add(ans);

        }

        // 单文本填空题、日期题
        if (Objects.equals(type, QuestionTypeEnum.WRITE.getCode()) || Objects.equals(type, QuestionTypeEnum.DATE.getCode())) {
            if (ObjectUtils.isEmpty(answerList)) {
                log.info("单文本填空题必填 qusId={}", qusId);
                AssertUtils.newThrow("单文本填空题必填");
            }
            ReportEntity.Answer ans = new ReportEntity.Answer();
            ans.setContent(answerList.get(0).getSubject());
            ansList.add(ans);

            // 题目是否需要上一题的答案，单文本填空题才有
            Integer isPrevAnswer = questionnaireQuestion.getIs_prev_answer();
            if (isPrevAnswer != null && isPrevAnswer == 1) {
                List<AnswerBean> answers = answerLists.get(index - 1);
                String title = quesTitle + answers.get(0).getSubject();
                ques.setTitle(title);
            } else {
                ques.setTitle(quesTitle);
            }
            return null;
        }

        // 多选题, 多条选填填空题, 量表题，必选
        if (Objects.equals(type, QuestionTypeEnum.MORE.getCode()) ||
                Objects.equals(type, QuestionTypeEnum.MORE_OPTIONAL_WRITE.getCode()) ||
                Objects.equals(type, QuestionTypeEnum.SCALE.getCode())) {
            ques.setTitle(quesTitle);

            if (Objects.equals(type, QuestionTypeEnum.MORE.getCode())) {
                if (ObjectUtils.isEmpty(answerList)) {
                    log.error("多选题必须选择一个 qusId={}, answerList={}", qusId, JsonUtils.toString(answerList));
                    AssertUtils.newThrow("多选题必须选择一个");
                }
            }

            List<ReportEntity.Answer> answers = answerList.stream()
                    .map(item -> {
                        ReportEntity.Answer ans = new ReportEntity.Answer();
                        String id = item.getId();
                        if (!optionIdList.contains(id)) {
                            log.error("问题和选项不匹配 qusId={}, optionId={}", qusId, id);
                            AssertUtils.newThrow("问题和选项不匹配");
                        }
                        String subject = item.getSubject();
                        QuestionnaireOptionExcel option = optionMap.get(id);
                        if (option == null) {
                            log.error("问题和选项不匹配1 qusId={}, optionId={}", qusId, id);
                            AssertUtils.newThrow("问题和选项不匹配1");
                        }
                        if (Objects.equals(type, QuestionTypeEnum.SCALE.getCode()) && StringUtils.isBlank(subject)) {
                            log.error("问题和选项不匹配2 qusId={}, optionId={}", qusId, id);
                            AssertUtils.newThrow("量表题必须都选择");
                        }
                        Integer isOther = option.getIs_other();
                        String content = option.getContent();

                        if (Objects.equals(type, QuestionTypeEnum.MORE.getCode())) {
                            if (isOther == null || isOther == 0) {
                                ans.setContent(option.getContent());
                            } else {
                                ans.setContent(subject);
                            }
                        } else {
                            ans.setTitle(content);
                            ans.setContent(subject);
                        }
                        return ans;
                    }).collect(Collectors.toList());
            ansList.addAll(answers);
        }
        for (AnswerBean item : answerList) {
            String id = item.getId();
            if (StringUtils.isBlank(id)) {
                continue;
            }
            // 单选选择题会走以下操作
            QuestionnaireOptionExcel option = optionMap.get(id);
            // 分支问题处理
            List<String> questionIdList = option.getQuestionIdList();
            if (questionIdList != null) {
                for (String quesIdBranch : questionIdList) {
                    String msg = handleSingleQuestion(quesIdBranch, i, answerLists, base, reportReflect, questionMap, optionMap);
                    if (msg != null) {
                        return msg;
                    }
                }
            }
        }
        return null;
    }

    /**
     * 设置数据库所需要的值
     *
     * @param filedName     设置的字段名
     * @param answerList    答案列表
     * @param reportReflect onboarding 问卷
     * @param optionMap
     */
    private void setFieldValue(String filedName, List<AnswerBean> answerList, ReportReflect reportReflect, Map<String, QuestionnaireOptionExcel> optionMap) {
        if (StringUtils.isBlank(filedName)) {
            return;
        }
        String subject = answerList.get(0).getSubject();
        String id = answerList.get(0).getId();
        if (StringUtils.isNotBlank(subject)) {
            ReflectUtil.setFieldValue(reportReflect, filedName, subject);
        } else {
            QuestionnaireOptionExcel option = optionMap.get(id);
            AssertUtils.isNull(option, "选项为空");
            String fieldValue = option.getFiled_value();
            ReflectUtil.setFieldValue(reportReflect, filedName, fieldValue);
        }
    }

    /**
     * 用户是否已经提交过该问卷
     *
     * @param type
     * @param userId
     * @return
     */
    public boolean isExist(Integer type, Long userId) {
        ReportEntity obj = new ReportEntity();
        obj.setUserId(userId);
        obj.setType(type);
        return mapper.selectOne(obj) != null;
    }

    public boolean save(ReportEntity base, ReportReflect reportReflect) {

        // 保存报告
        boolean is = save(base);
        AssertUtils.isFalse(is, "报告保存失败");
        return true;
    }


    /**
     * 获取单个id的报告
     *
     * @param id
     * @return
     */
    public ReportEntity get(Long id) {
        ReportEntity obj = get(id);
        if (obj != null) {
            return obj;
        }
        obj = getDb(id);
        if (obj != null) {
            setUserTypeRedis(obj);
            setRedis(obj);
        }
        return obj;
    }

    public ReportEntity getByType(Long accountId, Integer type) {
        ReportEntity report = get(accountId, type);
        if (report == null) {
            return null;
        }
        UserEntity user = userService.get(accountId);
        if (user != null) {
            report.setAvatarUrl(user.getAvatarUrl());
        }
        if (HExcelQuestionnaireService.QUESTIONNAIRE_TYPE_ONBORADING == type) {
            ReportOnboardingEntity onboardingReport = reportOnboardingService.get(report.getId());
            report.setExtend(onboardingReport);
        }
        return report;
    }
}

